package com.wurd.test.wx.token;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wurd.test.config.Config;
import com.wurd.test.exception.WxException;
import com.wurd.test.wx.utils.TokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Component
public class TokenBuilder {

    @Autowired
    private Cache cache;

    @Autowired
    private TokenBuilder tokenBuilder;

    @PostConstruct
    public void init(){
        new Thread(() -> {
            while (true) {
                log.debug("refresh auth token");
                try {
                    cache.getAppAuthRefreshToken().forEach((appId, authToken) -> {
                        if (authToken.isAutoRefresh()) {
                            try {
                                tokenBuilder.buildAuthAccessTokenWithRefresh(appId);
                                Thread.sleep(500);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "refresh auth token").start();
    }

    public AuthToken buildAuthAccessTokenWithRefresh(String appId) throws Exception {
        AuthToken token = cache.getAppAuthRefreshToken().get(appId);
        if (token == null) {
            throw new WxException("用户未授权");
        }
        if (!TokenUtils.verifyToken(token)) {
            Token cat = buildComponentAccessToken(buildComponentAccessTokenBody());
            JSONObject preParam = new JSONObject();
            preParam.put("component_access_token", cat.getToken());
            JSONObject preBody = new JSONObject();
            preBody.put("component_appid", Config.component_appid);
            preBody.put("authorizer_appid", appId);
            preBody.put("authorizer_refresh_token", token.getRefreshToken());
            JSONObject obj = TokenUtils.postAuthAccessToken(preParam, preBody);
            String authToken = obj.getString("authorizer_access_token");
            String refreshToken = obj.getString("authorizer_refresh_token");
            long expiresIn = obj.getLongValue("expires_in");
            long createTs = System.currentTimeMillis() / 1000;
            token.setToken(authToken);
            token.setRefreshToken(refreshToken);
            token.setCreateTs(createTs);
            token.setExpiresIn(expiresIn);
        }
        return cache.getAppAuthRefreshToken().get(appId);
    }

    /**
     * 用户微信授权后刷新token(权限可能变化)
     *
     * @param authCode
     * @return
     * @throws Exception
     */
    public AuthToken buildAuthAccessToken(String authCode) throws Exception {
        Token cat = buildComponentAccessToken(buildComponentAccessTokenBody());
        JSONObject preParam = new JSONObject();
        preParam.put("component_access_token", cat.getToken());
        JSONObject preBody = new JSONObject();
        preBody.put("component_appid", Config.component_appid);
        preBody.put("authorization_code", authCode);
        JSONObject authInfo = TokenUtils.postAuthRefreshToken(preParam, preBody);
        String authToken = authInfo.getString("authorizer_access_token");
        String authRefreshToken = authInfo.getString("authorizer_refresh_token");
        long expiresIn = authInfo.getLongValue("expires_in");
        String appId = authInfo.getString("authorizer_appid");
        JSONArray funcInfo = authInfo.getJSONArray("func_info");
        long createTs = System.currentTimeMillis() / 1000;
        cache.getAppAuthRefreshToken().put(appId,
                buildAuthToken(authToken, authRefreshToken, funcInfo, createTs, expiresIn));
        return cache.getAppAuthRefreshToken().get(appId);
    }

    private ReentrantLock preAuthCodeLock = new ReentrantLock();

    public Token buildPreAuthCode() throws Exception {
        if (!TokenUtils.verifyToken(cache.getPreAuthCode())) {
            preAuthCodeLock.lock();
            try {
                if (!TokenUtils.verifyToken(cache.getPreAuthCode())) {
                    Token cat = buildComponentAccessToken(buildComponentAccessTokenBody());
                    JSONObject preParam = new JSONObject();
                    preParam.put("component_access_token", cat.getToken());
                    JSONObject preBody = new JSONObject();
                    preBody.put("component_appid", Config.component_appid);
                    JSONObject obj = TokenUtils.postPreAuthCode(preParam, preBody);
                    String tokenStr = obj.getString("pre_auth_code");
                    long expiresIn = obj.getLongValue("expires_in");
                    long createTs = System.currentTimeMillis() / 1000;
                    cache.setPreAuthCode(buildToken(tokenStr, createTs, expiresIn));
                }
            } finally {
                preAuthCodeLock.unlock();
            }
        }
        return cache.getPreAuthCode();
    }

    public JSONObject buildComponentAccessTokenBody() throws Exception {
        JSONObject obj = new JSONObject();
        obj.put("component_appid", Config.component_appid);
        obj.put("component_appsecret", Config.component_appSecret);
        obj.put("component_verify_ticket", cache.getVerifyTicket().getToken());
        return obj;
    }

    private ReentrantLock componentAccessTokenLock = new ReentrantLock();

    public Token buildComponentAccessToken(JSONObject preBody) throws Exception {
        if (!TokenUtils.verifyToken(cache.getComponentAccessToken())) {
            componentAccessTokenLock.lock();
            try {
                if (!TokenUtils.verifyToken(cache.getComponentAccessToken())) {
                    JSONObject obj = TokenUtils.postComponentAccessToken(preBody);
                    String tokenStr = obj.getString("component_access_token");
                    long expiresIn = obj.getLongValue("expires_in");
                    long createTs = System.currentTimeMillis() / 1000;
                    cache.setComponentAccessToken(buildToken(tokenStr, createTs, expiresIn));
                }
            } finally {
                componentAccessTokenLock.unlock();
            }
        }
        return cache.getComponentAccessToken();
    }

    public Token buildVerifyTicket(String tokenStr, long createTs, long expiresIn) {
        if (!TokenUtils.verifyToken(cache.getVerifyTicket())) {
            cache.setVerifyTicket(buildToken(tokenStr, createTs, expiresIn));
        }
        return cache.getVerifyTicket();
    }

    public Token buildToken(String tokenStr, long createTs, long expiresIn) {
        return new Token(tokenStr, createTs, expiresIn);
    }

    public AuthToken buildAuthToken(String tokenStr, String refreshTokenStr,
                                           JSONArray funInfo,
                                           long createTs, long expiresIn) {
        return (AuthToken) new AuthToken()
                .setRefreshToken(refreshTokenStr)
                .setFuncInfo(funInfo)
                .setToken(tokenStr)
                .setCreateTs(createTs)
                .setExpiresIn(expiresIn);
    }

}
